﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Personal.BuildAsSilverlight4
{
    public class BuildHelper
    {
        private readonly List<string> _projectFiles = new List<string>();

        public string CreateSilverlightProject(string dotnetProjectFilePath)
        {
            var silverlightSolutionFilePath = GetAndCacheSilverlightProjectFileLocation(dotnetProjectFilePath);
            var silverlightXmlDoc = CreateBlankSilverlightProjectFile();
            var dotnetXmlDoc = XDocument.Load(dotnetProjectFilePath);

            //Copy and merge all the values from .NET project file to Silverlight project file.
            MergeAndUpdateSilverlightProjectFile(dotnetXmlDoc, silverlightXmlDoc);

            //save silverlight project
            silverlightXmlDoc.Save(silverlightSolutionFilePath);

            //Create silverlight project files for referenced projects
            foreach (var item in silverlightXmlDoc.Descendants(Constants.XMLTAG_PROJECTREFERENCE))
            {
                var projectReference = item.Attribute(Constants.XMLATTRIB_INCLUDE);
                var dotnetProjectFilePath1 = projectReference.Value.Replace(".SL.csproj", ".csproj");
                CreateSilverlightProject(dotnetProjectFilePath1);
            }

            return silverlightSolutionFilePath;
        }

        public string BuildSilverlightProject(string projectFileLocation)
        {
            var args = String.Format(" /property:Configuration=Release \"{0}\"", projectFileLocation);
            var buildProcess = CreateHiddenProcess(GetMsBuildExecutableLocation(), args);
            buildProcess.Start();
            var buildOutput = buildProcess.StandardOutput.ReadToEnd();
            CleanupProjectFiles();
            return buildOutput.TrimEnd();
        }

        #region Private Methods
        private void MergeAndUpdateSilverlightProjectFile(XDocument dotnetXmlDoc, XDocument silverlightXmlDoc)
        {
            //Simply copy attributes values
            XmlUtilities.CopyTagValue(dotnetXmlDoc, silverlightXmlDoc, Constants.XMLTAG_PROJECTGUID);
            XmlUtilities.CopyTagValue(dotnetXmlDoc, silverlightXmlDoc, Constants.XMLTAG_ROOTNAMESPACE);
            XmlUtilities.CopyTagValue(dotnetXmlDoc, silverlightXmlDoc, Constants.XMLTAG_ASSEMBLYNAME);
            XmlUtilities.CopyTagValue(dotnetXmlDoc, silverlightXmlDoc, Constants.XMLTAG_SIGNASSEMBLY);
            XmlUtilities.CopyTagValue(dotnetXmlDoc, silverlightXmlDoc, Constants.XMLTAG_ASSEMBLYORIGINATORKEYFILE);

            //Append .NET project file attributes to silverlight
            XmlUtilities.AppendTags(dotnetXmlDoc, silverlightXmlDoc, Constants.XMLTAG_COMPILE, Constants.XMLTAG_ITEMGROUP);
            XmlUtilities.AppendTags(dotnetXmlDoc, silverlightXmlDoc, Constants.XMLTAG_NONE, Constants.XMLTAG_ITEMGROUP);
            XmlUtilities.AppendTags(dotnetXmlDoc, silverlightXmlDoc, Constants.XMLTAG_PROJECTREFERENCE, Constants.XMLTAG_ITEMGROUP);

            //Merge and keep unique attributes
            XmlUtilities.UnionTags(dotnetXmlDoc, silverlightXmlDoc, Constants.XMLTAG_REFERENCE, Constants.XMLTAG_ITEMGROUP);

            //Append Suffix to assembly name
            UpdateNamesWithSuffix(silverlightXmlDoc);

            //Cleanup irrelevant references
            CleanupIrrelevantReferences(silverlightXmlDoc);
        }

        private static void CleanupIrrelevantReferences(XDocument silverlightXmlDoc)
        {
            silverlightXmlDoc.Descendants(Constants.XMLTAG_REFERENCE).Where(IsInvalidReference).Remove();
        }

        private static bool IsInvalidReference(XElement element)
        {
            var includeAttribute = element.Attribute(Constants.XMLATTRIB_INCLUDE);
            if (includeAttribute != null && includeAttribute.Value == Constants.DLL_SYSTEM_DATA || includeAttribute.Value == Constants.DLL_SYSTEM_DATA_EXTENSIONS)
                return true;
            return false;
        }

        private string GetAndCacheSilverlightProjectFileLocation(string dotnetProjectFilePath)
        {
            var silverlightProjectFileLocation = dotnetProjectFilePath.Replace(".csproj", ".SL.csproj");
            _projectFiles.Add(silverlightProjectFileLocation);
            return silverlightProjectFileLocation;
        }

        private static XDocument CreateBlankSilverlightProjectFile()
        {
            return XDocument.Parse(Resources.SLPROJ);
        }

        private static Process CreateHiddenProcess(string processFileName, string processArgs)
        {
            var processStartInfo = new ProcessStartInfo(processFileName, processArgs)
            {
                CreateNoWindow = true,
                WindowStyle = ProcessWindowStyle.Hidden,
                RedirectStandardOutput = true,
                UseShellExecute = false
            };
            return new Process { StartInfo = processStartInfo };
        }

        private static string GetMsBuildExecutableLocation()
        {
            var msbuildLocation = new StringBuilder(Environment.SystemDirectory);
            msbuildLocation.Remove(msbuildLocation.Length - 8, 8);
            msbuildLocation.Append("Microsoft.NET\\Framework\\");
            foreach (var directory in Directory.EnumerateDirectories(msbuildLocation.ToString()))
            {
                if (!directory.Contains("v4.")) continue;
                msbuildLocation.Clear();
                msbuildLocation.Append(directory);
            }
            msbuildLocation.Append("\\MSBuild.exe");
            return msbuildLocation.ToString();
        }

        private void UpdateNamesWithSuffix(XDocument xmlDoc)
        {
            //Modify assembly name in the csproj file
            var currentAssemblyName = xmlDoc.Descendants(Constants.XMLTAG_ASSEMBLYNAME).FirstOrDefault();
            if (currentAssemblyName != null)
                currentAssemblyName.SetValue(currentAssemblyName.Value + Constants.ASSEMBLY_SUFFIX);

            //Modify Project References
            foreach (var item in xmlDoc.Descendants(Constants.XMLTAG_PROJECTREFERENCE))
            {
                var projectReference = item.Attribute(Constants.XMLATTRIB_INCLUDE);
                var dotnetProjectFilePath = projectReference.Value;
                projectReference.SetValue(GetAndCacheSilverlightProjectFileLocation(dotnetProjectFilePath));
            }
        }

        private void CleanupProjectFiles()
        {
            foreach (var projectFile in _projectFiles.Where(File.Exists))
                File.Delete(projectFile);
        }
        #endregion
    }
}
